1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.hipaudio.audio;
12 
13 public import hip.hipaudio.audioclip;
14 public import hip.hipaudio.audiosource;
15 public import hip.api.audio;
16 import hip.hipaudio.config;
17 
18 //Backends
19 
20 static if(HasOpenAL){import hip.hipaudio.backend.openal.player;}
21 static if(HasOpenSLES){import hip.hipaudio.backend.opensles.player;}
22 static if(HasXAudio2){import hip.hipaudio.backend.xaudio.player;}
23 static if(HasAVAudioEngine){import hip.hipaudio.backend.avaudio.player;}
24 import hip.hipaudio.backend.nullaudio;
25 
26 
27 import hip.audio_decoding.audio;
28 import hip.math.utils:getClosestMultiple;
29 import hip.util.reflection;
30 import hip.error.handler;
31 import hip.hipaudio.backend.webaudio.player;
32 
33 version(Standalone)
34 {
35     alias HipAudioSourceAPI = HipAudioSource;
36     alias HipAudioClipAPI = HipAudioClip;
37 }
38 else
39 {
40     alias HipAudioSourceAPI = AHipAudioSource;
41     alias HipAudioClipAPI = IHipAudioClip;
42 }
43 
44 /** 
45  * This is an interface that should be created only once inside the application.
46  *  Every audio function is global, meaning that every AudioSource will refer to the player
47  */
48 public interface IHipAudioPlayer
49 {
50     //LOAD RELATED
51     public bool play_streamed(AHipAudioSource src);
52     public IHipAudioClip getClip();
53     public IHipAudioClip loadStreamed(string path, uint chunkSize);
54     public void updateStream(AHipAudioSource source);
55     public AHipAudioSource getSource(bool isStreamed);
56 
57     public void onDestroy();
58     public void update();
59 }
60 
61 class HipAudio
62 {
63     public static bool initialize(HipAudioImplementation implementation = HipAudioImplementation.OpenAL,
64     bool hasProAudio = false,
65     bool hasLowLatencyAudio = false,
66     int  optimalBufferSize = 4096,
67     int optimalSampleRate = 44_100)
68     {
69         ErrorHandler.startListeningForErrors("HipremeAudio initialization");
70         _hasInitializedAudio = true;
71         HipAudio.is3D = is3D;
72         audioInterface = getAudioInterface(implementation);
73         HipAudio.hasProAudio        = hasProAudio;
74         HipAudio.hasLowLatencyAudio = hasLowLatencyAudio;
75         HipAudio.optimalBufferSize  = optimalBufferSize;
76         HipAudio.optimalSampleRate  = optimalSampleRate;
77         return ErrorHandler.stopListeningForErrors();
78     }
79     @ExportD static bool pause(AHipAudioSource src)
80     {
81         src.isPlaying = false;
82         return false;
83     }
84     @ExportD static bool play_streamed(AHipAudioSource src)
85     {
86         audioInterface.play_streamed(src);
87         src.isPlaying = true;
88         return false;
89     }
90     @ExportD static IHipAudioClip getClip(){return audioInterface.getClip();}
91 
92     /**
93     *   Loads a file from disk, sets the chunkSize for streaming and does one decoding frame
94     */
95     @ExportD static IHipAudioClip loadStreamed(string path, uint chunkSize = ushort.max+1)
96     {
97         chunkSize = getClosestMultiple(optimalBufferSize, chunkSize);
98         HipAudioClip buf = cast(HipAudioClip)audioInterface.loadStreamed(path, chunkSize);
99         return buf;
100     }
101 
102     @ExportD static void updateStream(HipAudioSource source)
103     {
104         audioInterface.updateStream(source);
105     }
106     @ExportD static AHipAudioSource getSource(bool isStreamed = false, IHipAudioClip clip = null)
107     {
108         if(isStreamed) ErrorHandler.assertExit(clip !is null, "Can't get streamed source without any buffer");
109         HipAudioSource ret = cast(HipAudioSource)audioInterface.getSource(isStreamed);
110         if(clip)
111             ret.clip = clip;
112         return ret;
113     }
114     @ExportD static void onDestroy()
115     {
116         if(audioInterface !is null)
117             audioInterface.onDestroy();
118         audioInterface = null;
119     }
120 
121     static void update()
122     {
123         if(audioInterface !is null)
124             audioInterface.update();
125     }
126 
127     private static IHipAudioPlayer getAudioInterface(HipAudioImplementation impl,
128     bool hasProAudio = false,
129     bool hasLowLatencyAudio = false,
130     int  optimalBufferSize = 4096,
131     int optimalSampleRate = 44_100)
132     {
133         import hip.console.log;
134         final switch(impl)
135         {
136             case HipAudioImplementation.WebAudio:
137             {
138                 version(WebAssembly)
139                 {
140                     return new HipWebAudioPlayer(AudioConfig.musicConfig);
141                 }
142                 else
143                 {
144                     loglnWarn("Tried to use WebAudio implementation, but not in WebAssembly. No audio available");
145                     goto case HipAudioImplementation.Null;
146                 }
147             }
148             case HipAudioImplementation.OpenSLES:
149                 static if(HasOpenSLES)
150                 {
151                     return new HipOpenSLESAudioPlayer(AudioConfig.androidConfig,
152                     hasProAudio,
153                     hasLowLatencyAudio,
154                     optimalBufferSize,
155                     optimalSampleRate);
156                     break;
157                 }
158             case HipAudioImplementation.XAudio2:
159                 static if(HasXAudio2)
160                 {
161                     loglnInfo("Initializing XAudio2 with audio config ", AudioConfig.musicConfig);
162                     return new HipXAudioPlayer(AudioConfig.musicConfig);
163                 }
164                 else 
165                 {
166                     loglnWarn("Tried to use XAudio2 implementation, but no XAudio2 version was provided. OpenAL will be used instead");
167                     goto case HipAudioImplementation.OpenAL;
168                 }
169             case HipAudioImplementation.AVAudioEngine:
170             {
171                 static if(HasAVAudioEngine)
172                     return new HipAVAudioPlayer(AudioConfig.androidConfig);
173                 else
174                 {
175                     loglnWarn("Tried to use AVAudioEngine implementation, but no AVAudioEngine found. OpenAL will be used instead");
176                     goto case HipAudioImplementation.OpenAL;
177                 }
178             }
179             case HipAudioImplementation.OpenAL:
180             {
181                 static if(HasOpenAL)
182                 {
183                     //Please note that OpenAL HRTF(spatial sound) only works with Mono Channel
184                     return new HipOpenALAudioPlayer(AudioConfig.musicConfig);
185                 }
186                 else
187                 {
188                     loglnWarn("Tried to use OpenAL implementation, but no OpenAL version was provided. No audio available.");
189                     goto case HipAudioImplementation.Null;
190                 }
191             }
192             case HipAudioImplementation.Null:
193             {
194                 loglnWarn("No AudioInterface was found. Using NullAudio");
195                 return new HipNullAudio();
196             }
197         }
198     }
199 
200 
201    
202     protected __gshared bool hasProAudio;
203     protected __gshared bool hasLowLatencyAudio;
204     protected __gshared int  optimalBufferSize;
205     protected __gshared int  optimalSampleRate;
206     private   __gshared bool is3D;
207     private   __gshared uint activeSources;
208 
209     __gshared IHipAudioPlayer audioInterface;
210 
211     //Debug vars
212     private __gshared bool _hasInitializedAudio = false;
213     public bool hasInitializedAudio() => _hasInitializedAudio;
214 }